package com.eugenema.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

/**
 * 文件操作工具类
 *
 * @author Eugenema
 * @date 2023/10/29 19:49
 */
public class FileUtil {
    /** 日志 */
    private static Logger logger = LoggerFactory.getLogger(FileUtil.class);
    /** 项目根目录 */
    public static final String BASE_PATH = System.getProperty("user.dir") + File.separator;

    /**
     * 检查文件是否存在
     * <br/>仅检查项目根目录下的文件
     *
     * @param filePath 文件路径，包括文件扩展名。会前拼接项目根目录
     * @return 文件是否存在
     * @author Eugenema
     * @date 2023/10/29 19:50
     **/
    public static boolean exist(String filePath) {
        // 检查项目根目录下是否有该文件
        return new File(BASE_PATH + filePath).exists();
    }

    /**
     * 读取文本文件
     * <br/>仅读取项目根目录下的文件
     *
     * @param filePath 文件路径，包括文件扩展名。会前拼接项目根目录
     * @return 读取的文件内容，所有内容。若有换行，则拼接换行符。若读取异常，返回空字符串
     * @author Eugenema
     * @date 2023/10/29 19:56
     **/
    public static String read(String filePath) {
        // 读取文件，将内容拼接成一个字符串并返回
        String result = "";
        try {
            StringBuilder content = new StringBuilder();
            BufferedReader reader = new BufferedReader(new FileReader(BASE_PATH + filePath));
            String line;

            while ((line = reader.readLine()) != null) {
                content.append(line).append(System.lineSeparator());
            }

            reader.close();
            result = content.toString();
        } catch (IOException e) {
            // 记录异常信息
            logger.error("读取文件【{}】异常", BASE_PATH + filePath, e);
        }
        return result;
    }

    /**
     * 创建文件
     *
     * @param filePath 文件路径，包括扩展名，会前拼项目根目录路径
     * @return 创建成功或文本已存在返回 true，异常返回 false
     * @author Eugenema
     * @date 2023/10/29 20:29
     **/
    public static boolean createFile(String filePath) {
        File file = new File(BASE_PATH + filePath);
        if (!file.exists()) {
            try {
                return file.createNewFile();
            } catch (IOException e) {
                logger.error("创建文件【{}】异常", BASE_PATH + filePath, e);
                return false;
            }
        }
        logger.info("文件【{}】已存在", BASE_PATH + filePath);
        return true;
    }

    /**
     * 写文件
     *
     * @param filePath 文件路径，包括扩展名，前拼项目根目录路径
     * @param content  要写入的内容
     * @author Eugenema
     * @date 2023/10/29 20:35
     **/
    public static void write(String filePath, String content) {
        // 检查文件是否存在，若存在则写入指定内容
        if (exist(filePath)) {
            try (FileWriter writer = new FileWriter(BASE_PATH + filePath)) {
                writer.write(content);
            } catch (IOException e) {
                logger.error("写文件【{}】异常", BASE_PATH + filePath, e);
            }
        }
    }

    /**
     * 文件遍历
     * </br> 扫描指定路径下的所有指定扩展名文件，并返回文件列表
     * </br> 路径会加项目根目录（FileUtil.BASE_PATH）前缀
     *
     * @author 马寅广
     * @date 2023/12/31 13:43
     *
     * @param path 文件路径
     * @param extensionName 文件扩展名，若为空，则不检查扩展名
     * @param fileResult 文件列表，若为空则创建新列表，否则将文件列表添加到原列表中
     *
     * @return 文件列表
     **/
    public static List<File> explore(String path, String extensionName, List<File> fileResult) {
        if (fileResult == null) {
            fileResult = new ArrayList<>();
        }
        File file = new File(BASE_PATH + path);
        if (file.exists()) {
            File[] files = file.listFiles();
            if (files != null) {
                for (File f : files) {
                    if (f.isDirectory()) {
                        explore(f.getAbsolutePath().replace(BASE_PATH, ""), extensionName, fileResult);
                    } else {
                        if (extensionName == null || f.getName().toLowerCase().endsWith("." + extensionName.toLowerCase())) {
                            fileResult.add(f);
                        }
                    }
                }
            }
        } else {
            logger.error("路径【{}】不存在！", path);
        }
        return fileResult;
    }

    /**
     * 获取指定路径下的所有类
     * <br/> 例如：FileUtil.getAllClasses("com.eugenema.demand.impl")
     *
     * @author Eugenema
     * @date 2024/1/12 23:35
     *
     * @param packageName
     *
     * @return 所有类
     **/
    public static List<Class<?>> getAllClasses(String packageName) {
        List<Class<?>> classes = new ArrayList<>();
        try {
            String path = packageName.replace('.', '/');
            logger.debug("开始扫描：" + path);
            ClassLoader c = Thread.currentThread().getContextClassLoader();
            if (c != null) {
                Enumeration<URL> resources = c.getResources(path);
                while (resources.hasMoreElements()) {
                    logger.debug("开始处理资源：");
                    URL resource = resources.nextElement();
                    if (resource != null) {
                        String resourcePath = resource.getFile();
                        logger.debug("资源路径：{}", resourcePath);
                        // exe4j 打包后，获取到的路径格式为：file:/C:/Users/24392/AppData/Local/Temp/e4j7CE2.tmp_dir1705077074/selenium.jar!/com/eugenema/demand/impl
                        if (resourcePath != null && resourcePath.endsWith(".jar!/" + path)) {
                            logger.debug("开始处理jar包：" + resourcePath.replace("!/" + path, ""));
                            JarInputStream jarStream = new JarInputStream(new URL(resourcePath.replace("!/" + path, "")).openConnection().getInputStream());
                            JarEntry entry;
                            while ((entry = jarStream.getNextJarEntry()) != null) {
                                String className = entry.getName();
                                logger.debug("遍历jar包中的文件：{}", className);
                                if (className.startsWith(path) && className.endsWith(".class")) {
                                    logger.debug("匹配到类文件：{}", className);
                                    // 去除文件扩展名 ".class" 并转换为类名
                                    className = className.substring(0, className.length() - 6).replace('/', '.');
                                    try {
                                        classes.add(Class.forName(className));
                                    } catch (ClassNotFoundException e) {
                                        e.printStackTrace();
                                    }
                                }
                            }
                            logger.debug("jar包遍历结束");
                        } else {
                            logger.debug("资源文件不是 jar 文件");
                            File directory = new File(resourcePath);
                            logger.debug("文件路径是否存在：" + directory.exists());
                            if (directory != null && directory.exists() && directory.isDirectory()) {
                                for (File file : directory.listFiles()) {
                                    if (file != null && file.isFile() && file.getName().endsWith(".class")) {
                                        classes.add(Class.forName(path.replace('/', '.') + "." + file.getName().replace(".class", "")));
                                    } else if (file != null && file.isDirectory()) {
                                        classes.addAll(getAllClasses(path + "." + file.getName()));
                                    }
                                }
                            }
                        }
                    } else {
                        // 如果资源为空，则表示找不到指定的包或目录，这里可以抛出异常或返回空列表，根据需要进行处理。这里我们只是简单地忽略这种情况。
                        logger.error("找不到指定的包或目录，资源为空");
                    }
                }
            } else {
                // 如果无法获取类加载器，则表示无法获取线程的上下文类加载器，这里可以抛出异常或返回空列表，根据需要进行处理。这里我们只是简单地抛出一个异常。
                logger.error("无法获取类加载器，无法获取指定包下的所有类");
                throw new RuntimeException("无法获取线程的上下文类加载器");
            }
        } catch (Exception e) {
            logger.error("获取指定包下的所有类失败", e);
            // 处理异常情况，例如输入参数不合法或文件访问权限问题等。这里我们只是简单地打印异常信息，根据需要进行处理。
            e.printStackTrace();
        } finally {
            return classes;
        }
    }
}
